Flutter Navigator Push and Pop
Navigation is an essential part of almost every Flutter application. It allows users to move from one screen to another, open detail pages, complete multi-step workflows, and return to previously visited screens.
In Flutter, screens and pages are represented as routes. The Navigator manages these routes using a stack. Navigator.push() adds a new route to the stack, while Navigator.pop() removes the current route and reveals the previous route. Flutter Navigation Basics
1. What Is Navigator in Flutter?
Navigator is a Flutter widget that manages a stack of routes. It provides methods for moving between screens and controlling the navigation history of an application.
Think of the Navigator as a stack of pages:
Initial Screen
↓
Second Screen
↓
Third Screen
When a new screen is opened, it is placed on top of the stack. When the current screen is closed, the top route is removed and the previous screen becomes visible.
2. What Is a Route?
In Flutter, a screen or page is commonly represented as a Route. A route describes a screen that can be placed into the Navigator's navigation stack.
For example:
HomeScreen
ProductScreen
ProfileScreen
SettingsScreen
Each of these can participate in Flutter navigation as a route.
A MaterialPageRoute is commonly used in Material applications, while CupertinoPageRoute provides an iOS-style transition. Flutter Navigation Cookbook
3. Understanding the Navigation Stack
Flutter's Navigator works using a stack-based navigation model.
Suppose the application starts with the Home screen:
Stack:
[Home]
When the user opens the Products screen:
Stack:
[Home]
[Products]
When the user opens Product Details:
Stack:
[Home]
[Products]
[Product Details]
When Navigator.pop() is called:
Stack:
[Home]
[Products]
The Product Details route is removed and the Products screen becomes visible again.
4. Navigator.push()
Navigator.push() adds a new route to the Navigator's stack and displays that route above the current screen.
The basic syntax is:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
The Flutter API defines Navigator.push() as returning a Future. That Future completes when the pushed route is popped, optionally providing the value supplied to Navigator.pop(). Navigator.push API
5. Basic Navigator.push() Example
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
),
);
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
},
child: const Text('Open Second Screen'),
),
),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: const Center(
child: Text('Welcome to Second Screen'),
),
);
}
}
When the button is pressed, Flutter creates the second route and pushes it onto the navigation stack.
6. Breaking Down Navigator.push()
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
| Part | Purpose |
Navigator | Manages navigation routes. |
push() | Adds a new route to the stack. |
context | Identifies the Navigator associated with the widget tree. |
MaterialPageRoute | Creates a Material-style route. |
builder | Builds the destination screen. |
SecondScreen() | The screen being opened. |
7. Navigator.pop()
Navigator.pop() removes the current route from the navigation stack and returns the user to the previous route.
Basic syntax:
Navigator.pop(context);
The official Flutter navigation recipe uses Navigator.pop() to return from the second route to the first route. Navigate to a new screen and back
8. Basic Navigator.pop() Example
class SecondScreen extends StatelessWidget {
const SecondScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go Back'),
),
),
);
}
}
When the button is pressed, the current route is removed from the stack.
9. Push and Pop Together
push() and pop() are normally used together.
Home
|
| Navigator.push()
v
Details
|
| Navigator.pop()
v
Home
This is one of the most basic navigation patterns in Flutter.
10. Complete Push and Pop Example
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
},
child: const Text('Open Details'),
),
),
);
}
}
class DetailsScreen extends StatelessWidget {
const DetailsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Details'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go Back'),
),
),
);
}
}
11. Navigation Flow
Application starts
↓
HomeScreen
↓
User taps "Open Details"
↓
Navigator.push()
↓
DetailsScreen
↓
User taps "Go Back"
↓
Navigator.pop()
↓
HomeScreen
12. Using Navigator.of(context)
Instead of calling:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
You can explicitly retrieve the nearest Navigator:
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
Similarly:
Navigator.of(context).pop();
Navigator.of(context) retrieves the nearest Navigator associated with the supplied BuildContext. Flutter Stack-Based Navigation
13. MaterialPageRoute
MaterialPageRoute creates a route with Material Design page-transition behavior.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfileScreen(),
),
);
It is commonly used when building applications with MaterialApp.
Example
MaterialPageRoute(
builder: (context) => const ProfileScreen(),
)
14. CupertinoPageRoute
For iOS-style navigation transitions, Flutter provides CupertinoPageRoute.
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => const ProfileScreen(),
),
);
It can be useful when an application wants Cupertino-style page transitions.
15. Passing Data with Navigator.push()
Navigation often requires sending information from one screen to another. For example, a product list can open a product details screen and pass the selected product.
Product Model
class Product {
final String name;
final double price;
const Product({
required this.name,
required this.price,
});
}
Passing the Product
final product = Product(
name: 'Laptop',
price: 60000,
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsScreen(
product: product,
),
),
);
Receiving the Product
class ProductDetailsScreen extends StatelessWidget {
final Product product;
const ProductDetailsScreen({
super.key,
required this.product,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(product.name),
),
body: Center(
child: Text(
'Price: ₹${product.price}',
),
),
);
}
}
Flutter's navigation cookbook documents passing objects to a new route, including passing data through a screen constructor. Send data to a new screen
16. Returning Data with Navigator.pop()
Navigator.pop() can also return a value to the previous screen.
For example:
Navigator.pop(context, 'Selected');
The previous screen can wait for the result:
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
print(result);
The Navigator API specifies that the Future returned by push() completes with the result passed to pop(). Navigator API
17. Complete Example of Returning Data
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State createState() => _HomeScreenState();
}
class _HomeScreenState extends State {
String selectedValue = 'Nothing selected';
Future openSelectionScreen() async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
if (!mounted) return;
setState(() {
selectedValue = result ?? 'Nothing selected';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(selectedValue),
const SizedBox(height: 20),
ElevatedButton(
onPressed: openSelectionScreen,
child: const Text('Select Option'),
),
],
),
),
);
}
}
class SelectionScreen extends StatelessWidget {
const SelectionScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Select Option'),
),
body: Column(
children: [
ListTile(
title: const Text('Flutter'),
onTap: () {
Navigator.pop(context, 'Flutter');
},
),
ListTile(
title: const Text('Dart'),
onTap: () {
Navigator.pop(context, 'Dart');
},
),
],
),
);
}
}
Flutter's official cookbook demonstrates this pattern for returning a selection from one screen to another. Return data from a screen
18. Navigator.push() with Generic Types
Flutter navigation methods can use generic types to describe the value returned when a route is popped.
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
Here, String indicates that the route can return a String value.
For a boolean result:
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ConfirmationScreen(),
),
);
19. Push Multiple Screens
You can push multiple routes sequentially.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ScreenA(),
),
);
Then from Screen A:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ScreenB(),
),
);
The stack becomes:
[Home]
[Screen A]
[Screen B]
Calling:
Navigator.pop(context);
removes Screen B:
[Home]
[Screen A]
20. Multiple Pop Operations
Sometimes an application needs to go back through several screens. popUntil() can remove routes until a specified condition is satisfied.
Navigator.popUntil(
context,
(route) => route.isFirst,
);
This removes routes until the first route in the stack is reached.
21. Navigator.pushReplacement()
pushReplacement() pushes a new route and replaces the current route.
This can be useful for flows such as login to home.
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
);
Before:
[Login]
After:
[Home]
The Login route is replaced instead of remaining below Home.
22. Navigator.pushAndRemoveUntil()
pushAndRemoveUntil() adds a new route and removes previous routes until a condition is met.
For example, after completing checkout, you may want to navigate to the home screen and remove previous checkout pages:
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
(route) => false,
);
The result is effectively:
Before:
Home
Cart
Checkout
Payment
Success
After:
Home
Flutter's navigation cookbook lists pushAndRemoveUntil, pushReplacement, popUntil, and other Navigator methods for more advanced stack manipulation. Flutter Navigation Basics
23. Navigator.popUntil()
popUntil() repeatedly removes routes until the supplied condition becomes true.
Navigator.popUntil(
context,
(route) => route.isFirst,
);
For example:
Home
Products
Product Details
Checkout
After popUntil(route.isFirst):
Home
24. Navigation After Login
A common application flow is:
Login
↓
Home
↓
Products
↓
Product Details
After successful authentication, the login screen may be replaced:
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
);
This prevents the user from returning to the login page through normal back navigation.
25. Navigation After Checkout
Suppose an e-commerce application has:
Home
↓
Products
↓
Cart
↓
Checkout
↓
Payment
↓
Order Success
After a successful order, the application may clear the checkout flow and return to Home:
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
(route) => false,
);
26. Using Back Button with Navigator.pop()
Flutter's AppBar can automatically provide a back button when the current route has a previous route to return to.
You can also create your own back button:
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
)
Another example:
ElevatedButton.icon(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back),
label: const Text('Back'),
)
27. Checking Whether a Route Can Be Popped
Before manually popping a route, you may need to determine whether there is a previous route in the Navigator stack.
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
This can be useful when implementing custom navigation controls.
28. Navigator.push() vs pushReplacement()
| Method | Behavior | Typical Use |
push() | Adds a new route | Home → Details |
pushReplacement() | Replaces current route | Login → Home |
pushAndRemoveUntil() | Adds route and removes routes based on condition | Checkout → Home |
pop() | Removes current route | Details → Home |
popUntil() | Removes routes until a condition is met | Return to first route |
29. Navigator.push() vs Navigator.pop()
| push() | pop() |
| Adds a route | Removes a route |
| Moves forward | Moves backward |
| Opens a new screen | Closes the current screen |
| Returns a Future | Can return a result |
| Increases stack size | Decreases stack size |
30. Navigator Stack Example
Step 1:
[Home]
Step 2:
Navigator.push(Products)
[Home]
[Products]
Step 3:
Navigator.push(Details)
[Home]
[Products]
[Details]
Step 4:
Navigator.pop()
[Home]
[Products]
Step 5:
Navigator.pop()
[Home]
31. Practical Product Navigation Example
class ProductListScreen extends StatelessWidget {
const ProductListScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
),
body: ListView(
children: [
ListTile(
leading: const Icon(Icons.phone_android),
title: const Text('Smartphone'),
subtitle: const Text('₹25,000'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductDetailsScreen(
name: 'Smartphone',
price: 25000,
),
),
);
},
),
ListTile(
leading: const Icon(Icons.laptop),
title: const Text('Laptop'),
subtitle: const Text('₹60,000'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductDetailsScreen(
name: 'Laptop',
price: 60000,
),
),
);
},
),
],
),
);
}
}
class ProductDetailsScreen extends StatelessWidget {
final String name;
final double price;
const ProductDetailsScreen({
super.key,
required this.name,
required this.price,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Product Details'),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Text(
'Price: ₹$price',
style: const TextStyle(
fontSize: 20,
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Back to Products'),
),
],
),
),
);
}
}
32. Passing Objects Through Navigator.push()
For larger applications, passing a complete model object is often cleaner than passing many individual values.
class Course {
final String title;
final String description;
final double price;
const Course({
required this.title,
required this.description,
required this.price,
});
}
Push the course:
final course = Course(
title: 'Flutter Training',
description: 'Learn Flutter application development.',
price: 15000,
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CourseDetailsScreen(
course: course,
),
),
);
33. Returning a Boolean Result
A common use case is asking the user to confirm an action.
final confirmed = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ConfirmationScreen(),
),
);
if (confirmed == true) {
print('User confirmed');
} else {
print('User cancelled');
}
On the confirmation screen:
ElevatedButton(
onPressed: () {
Navigator.pop(context, true);
},
child: const Text('Confirm'),
)
Cancel button:
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text('Cancel'),
)
34. Important Difference Between pop() and pushReplacement()
Consider:
[Home]
[Login]
If you call:
Navigator.pop(context);
the Login route is removed and Home becomes visible.
If you call:
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
);
the current route is replaced with Home.
Choosing between them depends on the desired navigation history.
35. Common Mistakes with Navigator.push()
Mistake 1: Forgetting BuildContext
Navigation requires a valid BuildContext.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
Mistake 2: Forgetting the Destination Screen
The route needs a widget to build:
builder: (context) => const DetailsScreen()
Mistake 3: Pushing the Same Screen Repeatedly
Repeatedly calling push() can create many duplicate routes in the navigation stack.
Mistake 4: Using pop() Without Considering the Stack
Before custom back navigation, you can check:
Navigator.canPop(context)
Mistake 5: Ignoring the Returned Future
If the destination screen returns information, await the Future returned by Navigator.push().
36. Navigator.push() and Async Programming
Because Navigator.push() returns a Future, it can be used with async and await.
Future openDetails() async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
print(result);
}
The Future completes when the destination route is popped.
37. Safe Context Usage After await
If an asynchronous navigation result is followed by a widget update, check that the widget is still mounted before using its context or state.
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
if (!mounted) return;
setState(() {
selectedValue = result ?? '';
});
This helps avoid using a State object after it has been removed from the widget tree.
38. Navigator and Bottom Navigation
NavigationBar and Navigator can be used together.
For example:
Home
Products
Orders
Profile
|
+-- Settings
+-- Edit Profile
The NavigationBar can switch between top-level sections, while Navigator.push() can open detail screens inside a section.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SettingsScreen(),
),
);
39. Navigator and Forms
Navigation is frequently used when opening forms.
ElevatedButton(
onPressed: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const EditProfileScreen(),
),
);
if (!mounted) return;
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(result),
),
);
}
},
child: const Text('Edit Profile'),
)
The form screen can return a message:
Navigator.pop(
context,
'Profile updated successfully',
);
40. Navigator and Dialogs
Some Flutter APIs such as dialogs and modal bottom sheets also use route-based mechanisms internally.
For example:
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Delete Item?'),
content: const Text(
'Do you want to delete this item?',
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Delete'),
),
],
);
},
);
Here, Navigator.pop() dismisses the dialog route.
41. Named Routes and Navigator
Flutter supports named routes through Navigator.pushNamed().
MaterialApp(
routes: {
'/': (context) => const HomeScreen(),
'/details': (context) => const DetailsScreen(),
},
);
Navigate using:
Navigator.pushNamed(
context,
'/details',
);
Return using:
Navigator.pop(context);
Named routes remain part of Flutter, but current Flutter documentation does not recommend them for most new applications. For many new applications, Flutter recommends using Navigator with MaterialPageRoute or a routing package such as go_router, particularly when advanced routing or deep linking is required. Flutter Navigation and Routing
42. Navigator.push() with Named Routes
Navigator.pushNamed(
context,
'/profile',
);
This approach can be useful when working with an existing application that already uses named routes.
For new applications, direct route construction or a modern routing solution may be more appropriate depending on the application's requirements.
43. Nested Navigator
Large applications may contain more than one Navigator. A nested Navigator can manage navigation within a particular section or workflow.
Main Navigator
│
├── Home
├── Products
└── Profile
│
└── Nested Navigator
├── Profile Home
├── Edit Profile
└── Settings
This approach can be useful when an individual section needs its own navigation history.
44. NavigatorObserver
NavigatorObserver can be used to observe navigation events such as routes being pushed or popped.
class MyNavigatorObserver extends NavigatorObserver {
@override
void didPush(Route route, Route? previousRoute) {
print('Route pushed: ${route.settings.name}');
}
@override
void didPop(Route route, Route? previousRoute) {
print('Route popped: ${route.settings.name}');
}
}
It can be registered with MaterialApp:
MaterialApp(
navigatorObservers: [
MyNavigatorObserver(),
],
home: const HomeScreen(),
);
This can be useful for analytics, logging, and monitoring navigation behavior.
45. Practical E-Commerce Navigation Flow
Home
|
+-- Products
| |
| +-- Product Details
| |
| +-- Add to Cart
|
+-- Cart
| |
| +-- Checkout
| |
| +-- Payment
| |
| +-- Order Success
|
+-- Profile
|
+-- Edit Profile
+-- Settings
Different Navigator operations can be used for different stages of this flow.
| Action | Navigation Method |
| Open Product Details | push() |
| Return to Products | pop() |
| Login → Home | pushReplacement() |
| Checkout → Home after completion | pushAndRemoveUntil() |
| Return to first screen | popUntil() |
46. Complete Mini Project
import 'package:flutter/material.dart';
void main() {
runApp(const NavigationApp());
}
class NavigationApp extends StatelessWidget {
const NavigationApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Navigator Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
),
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Welcome to the Application',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const DetailsScreen(),
),
);
if (!context.mounted) return;
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(result),
),
);
}
},
child: const Text('Open Details'),
),
],
),
),
);
}
}
class DetailsScreen extends StatelessWidget {
const DetailsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Details'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'This is the Details Screen',
style: TextStyle(fontSize: 20),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(
context,
'Returned from Details Screen',
);
},
child: const Text('Return Data'),
),
const SizedBox(height: 10),
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go Back'),
),
],
),
),
);
}
}
47. Execution Flow of the Mini Project
- The application starts on
HomeScreen.
- The user taps Open Details.
Navigator.push() adds DetailsScreen to the stack.
- The Details screen appears.
- The user can press Go Back.
Navigator.pop() removes the Details route.
- The user can also press Return Data.
Navigator.pop(context, result) returns a String.
- The Future returned by
Navigator.push() receives that String.
- The Home screen displays the returned result.
48. Important Navigator Methods
| Method | Description |
push() | Adds a route to the navigation stack. |
pop() | Removes the current route. |
pushReplacement() | Replaces the current route with another route. |
pushAndRemoveUntil() | Pushes a route and removes previous routes according to a condition. |
popUntil() | Pops routes until a condition is satisfied. |
pushNamed() | Pushes a route using a registered route name. |
canPop() | Checks whether the Navigator can pop a route. |
of() | Retrieves the nearest Navigator. |
49. Common Interview Questions
Q1. What is Navigator in Flutter?
Navigator is a widget that manages a stack of routes and provides methods for navigating between screens.
Q2. What does Navigator.push() do?
It adds a new route to the Navigator stack and displays it above the current route.
Q3. What does Navigator.pop() do?
It removes the current route from the stack and reveals the previous route.
Q4. What is MaterialPageRoute?
It is a route implementation that provides Material-style page transitions.
Q5. Can Navigator.pop() return data?
Yes. A value can be supplied as the second argument:
Navigator.pop(context, result);
Q6. Can Navigator.push() receive returned data?
Yes. Navigator.push() returns a Future that completes when the route is popped.
Q7. What is the difference between push() and pop()?
push() adds a route, while pop() removes the current route.
Q8. What is pushReplacement()?
It replaces the current route with a new route.
Q9. What is pushAndRemoveUntil()?
It pushes a new route and removes previous routes until the supplied condition is satisfied.
Q10. What is popUntil()?
It removes routes until a specified condition becomes true.
50. Practice Exercises
- Create a Home screen and Details screen.
- Navigate from Home to Details using
Navigator.push().
- Return to Home using
Navigator.pop().
- Create a Product model and pass it to Product Details.
- Return a selected product from a selection screen.
- Create Login and Home screens and use
pushReplacement().
- Create Home, Cart, Checkout, and Success screens.
- Use
pushAndRemoveUntil() after checkout.
- Use
popUntil() to return to the Home screen.
- Implement a confirmation screen that returns a Boolean result.
- Create a reusable navigation helper method.
- Experiment with
MaterialPageRoute and CupertinoPageRoute.
- Implement a nested Navigator for a Profile section.
- Add a NavigatorObserver to log route changes.
51. Best Practices
- Use
Navigator.push() when adding a new screen to the navigation stack.
- Use
Navigator.pop() to return to the previous screen.
- Use meaningful route structures for larger applications.
- Pass strongly typed objects when practical.
- Use generic types when returning values from routes.
- Use
pushReplacement() when the current route should no longer remain in the normal back stack.
- Use
pushAndRemoveUntil() for flows where previous routes should be removed.
- Use
popUntil() when returning to a specific point in the navigation stack.
- Check
mounted after awaiting navigation results before updating widget state.
- Use nested navigation when a subsection requires an independent navigation flow.
- For complex deep-linking requirements, consider Router-based navigation or a routing package such as
go_router.
52. Quick Revision
| Concept | Meaning |
| Navigator | Manages navigation routes as a stack. |
| Route | Represents a screen/page in navigation. |
| push() | Adds a new route. |
| pop() | Removes the current route. |
| pushReplacement() | Replaces the current route. |
| pushAndRemoveUntil() | Pushes a route and removes previous routes according to a condition. |
| popUntil() | Pops routes until a condition is met. |
| MaterialPageRoute | Material-style route. |
| CupertinoPageRoute | Cupertino-style route. |
| canPop() | Checks whether the current Navigator can pop. |
| pushNamed() | Pushes a registered named route. |
53. Key Takeaways
- Flutter uses a Navigator to manage a stack of routes.
Navigator.push() opens a new screen by adding a route to the stack.
Navigator.pop() closes the current screen by removing its route.
- A route can return data through
Navigator.pop(context, result).
- The Future returned by
Navigator.push() can receive that returned result.
MaterialPageRoute is commonly used for Material applications.
CupertinoPageRoute provides Cupertino-style transitions.
pushReplacement() is useful when replacing the current navigation entry.
pushAndRemoveUntil() is useful for clearing previous navigation history according to a condition.
popUntil() can return to an earlier point in the navigation stack.
- Navigator can be combined with bottom navigation and nested navigation.
- For advanced navigation and deep-linking requirements, Flutter supports Router-based navigation and routing packages.
54. Useful Resources
Flutter Navigation and Routing: Official Flutter Navigation Documentation
Navigator API: Official Navigator API Documentation
Navigate to a New Screen and Back: Flutter Navigation Basics
Passing Data: Send Data to a New Screen
Returning Data: Return Data from a Screen
JustAcademy Flutter Training: JustAcademy Flutter Training Course
Register for Flutter Course Demo: Register for Flutter Course Demo
Flutter Navigator Push and Pop
Navigation is an essential part of almost every Flutter application. It allows users to move from one screen to another, open detail pages, complete multi-step workflows, and return to previously visited screens.
In Flutter, screens and pages are represented as routes. The Navigator manages these routes using a stack. Navigator.push() adds a new route to the stack, while Navigator.pop() removes the current route and reveals the previous route. Flutter Navigation Basics
1. What Is Navigator in Flutter?
Navigator is a Flutter widget that manages a stack of routes. It provides methods for moving between screens and controlling the navigation history of an application.
Think of the Navigator as a stack of pages:
Initial Screen
↓
Second Screen
↓
Third Screen
When a new screen is opened, it is placed on top of the stack. When the current screen is closed, the top route is removed and the previous screen becomes visible.
2. What Is a Route?
In Flutter, a screen or page is commonly represented as a Route. A route describes a screen that can be placed into the Navigator's navigation stack.
For example:
HomeScreen
ProductScreen
ProfileScreen
SettingsScreen
Each of these can participate in Flutter navigation as a route.
A MaterialPageRoute is commonly used in Material applications, while CupertinoPageRoute provides an iOS-style transition. Flutter Navigation Cookbook
3. Understanding the Navigation Stack
Flutter's Navigator works using a stack-based navigation model.
Suppose the application starts with the Home screen:
Stack:
[Home]
When the user opens the Products screen:
Stack:
[Home]
[Products]
When the user opens Product Details:
Stack:
[Home]
[Products]
[Product Details]
When Navigator.pop() is called:
Stack:
[Home]
[Products]
The Product Details route is removed and the Products screen becomes visible again.
4. Navigator.push()
Navigator.push() adds a new route to the Navigator's stack and displays that route above the current screen.
The basic syntax is:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
The Flutter API defines Navigator.push() as returning a Future. That Future completes when the pushed route is popped, optionally providing the value supplied to Navigator.pop(). Navigator.push API
5. Basic Navigator.push() Example
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
),
);
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
},
child: const Text('Open Second Screen'),
),
),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: const Center(
child: Text('Welcome to Second Screen'),
),
);
}
}
When the button is pressed, Flutter creates the second route and pushes it onto the navigation stack.
6. Breaking Down Navigator.push()
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
| Part | Purpose |
Navigator | Manages navigation routes. |
push() | Adds a new route to the stack. |
context | Identifies the Navigator associated with the widget tree. |
MaterialPageRoute | Creates a Material-style route. |
builder | Builds the destination screen. |
SecondScreen() | The screen being opened. |
7. Navigator.pop()
Navigator.pop() removes the current route from the navigation stack and returns the user to the previous route.
Basic syntax:
Navigator.pop(context);
The official Flutter navigation recipe uses Navigator.pop() to return from the second route to the first route. Navigate to a new screen and back
8. Basic Navigator.pop() Example
class SecondScreen extends StatelessWidget {
const SecondScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go Back'),
),
),
);
}
}
When the button is pressed, the current route is removed from the stack.
9. Push and Pop Together
push() and pop() are normally used together.
Home
|
| Navigator.push()
v
Details
|
| Navigator.pop()
v
Home
This is one of the most basic navigation patterns in Flutter.
10. Complete Push and Pop Example
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
},
child: const Text('Open Details'),
),
),
);
}
}
class DetailsScreen extends StatelessWidget {
const DetailsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Details'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go Back'),
),
),
);
}
}
11. Navigation Flow
Application starts
↓
HomeScreen
↓
User taps "Open Details"
↓
Navigator.push()
↓
DetailsScreen
↓
User taps "Go Back"
↓
Navigator.pop()
↓
HomeScreen
12. Using Navigator.of(context)
Instead of calling:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
You can explicitly retrieve the nearest Navigator:
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
Similarly:
Navigator.of(context).pop();
Navigator.of(context) retrieves the nearest Navigator associated with the supplied BuildContext. Flutter Stack-Based Navigation
13. MaterialPageRoute
MaterialPageRoute creates a route with Material Design page-transition behavior.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfileScreen(),
),
);
It is commonly used when building applications with MaterialApp.
Example
MaterialPageRoute(
builder: (context) => const ProfileScreen(),
)
14. CupertinoPageRoute
For iOS-style navigation transitions, Flutter provides CupertinoPageRoute.
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => const ProfileScreen(),
),
);
It can be useful when an application wants Cupertino-style page transitions.
15. Passing Data with Navigator.push()
Navigation often requires sending information from one screen to another. For example, a product list can open a product details screen and pass the selected product.
Product Model
class Product {
final String name;
final double price;
const Product({
required this.name,
required this.price,
});
}
Passing the Product
final product = Product(
name: 'Laptop',
price: 60000,
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsScreen(
product: product,
),
),
);
Receiving the Product
class ProductDetailsScreen extends StatelessWidget {
final Product product;
const ProductDetailsScreen({
super.key,
required this.product,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(product.name),
),
body: Center(
child: Text(
'Price: ₹${product.price}',
),
),
);
}
}
Flutter's navigation cookbook documents passing objects to a new route, including passing data through a screen constructor. Send data to a new screen
16. Returning Data with Navigator.pop()
Navigator.pop() can also return a value to the previous screen.
For example:
Navigator.pop(context, 'Selected');
The previous screen can wait for the result:
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
print(result);
The Navigator API specifies that the Future returned by push() completes with the result passed to pop(). Navigator API
17. Complete Example of Returning Data
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State createState() => _HomeScreenState();
}
class _HomeScreenState extends State {
String selectedValue = 'Nothing selected';
Future openSelectionScreen() async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
if (!mounted) return;
setState(() {
selectedValue = result ?? 'Nothing selected';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(selectedValue),
const SizedBox(height: 20),
ElevatedButton(
onPressed: openSelectionScreen,
child: const Text('Select Option'),
),
],
),
),
);
}
}
class SelectionScreen extends StatelessWidget {
const SelectionScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Select Option'),
),
body: Column(
children: [
ListTile(
title: const Text('Flutter'),
onTap: () {
Navigator.pop(context, 'Flutter');
},
),
ListTile(
title: const Text('Dart'),
onTap: () {
Navigator.pop(context, 'Dart');
},
),
],
),
);
}
}
Flutter's official cookbook demonstrates this pattern for returning a selection from one screen to another. Return data from a screen
18. Navigator.push() with Generic Types
Flutter navigation methods can use generic types to describe the value returned when a route is popped.
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
Here, String indicates that the route can return a String value.
For a boolean result:
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ConfirmationScreen(),
),
);
19. Push Multiple Screens
You can push multiple routes sequentially.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ScreenA(),
),
);
Then from Screen A:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ScreenB(),
),
);
The stack becomes:
[Home]
[Screen A]
[Screen B]
Calling:
Navigator.pop(context);
removes Screen B:
[Home]
[Screen A]
20. Multiple Pop Operations
Sometimes an application needs to go back through several screens. popUntil() can remove routes until a specified condition is satisfied.
Navigator.popUntil(
context,
(route) => route.isFirst,
);
This removes routes until the first route in the stack is reached.
21. Navigator.pushReplacement()
pushReplacement() pushes a new route and replaces the current route.
This can be useful for flows such as login to home.
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
);
Before:
[Login]
After:
[Home]
The Login route is replaced instead of remaining below Home.
22. Navigator.pushAndRemoveUntil()
pushAndRemoveUntil() adds a new route and removes previous routes until a condition is met.
For example, after completing checkout, you may want to navigate to the home screen and remove previous checkout pages:
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
(route) => false,
);
The result is effectively:
Before:
Home
Cart
Checkout
Payment
Success
After:
Home
Flutter's navigation cookbook lists pushAndRemoveUntil, pushReplacement, popUntil, and other Navigator methods for more advanced stack manipulation. Flutter Navigation Basics
23. Navigator.popUntil()
popUntil() repeatedly removes routes until the supplied condition becomes true.
Navigator.popUntil(
context,
(route) => route.isFirst,
);
For example:
Home
Products
Product Details
Checkout
After popUntil(route.isFirst):
Home
24. Navigation After Login
A common application flow is:
Login
↓
Home
↓
Products
↓
Product Details
After successful authentication, the login screen may be replaced:
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
);
This prevents the user from returning to the login page through normal back navigation.
25. Navigation After Checkout
Suppose an e-commerce application has:
Home
↓
Products
↓
Cart
↓
Checkout
↓
Payment
↓
Order Success
After a successful order, the application may clear the checkout flow and return to Home:
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
(route) => false,
);
26. Using Back Button with Navigator.pop()
Flutter's AppBar can automatically provide a back button when the current route has a previous route to return to.
You can also create your own back button:
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
)
Another example:
ElevatedButton.icon(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back),
label: const Text('Back'),
)
27. Checking Whether a Route Can Be Popped
Before manually popping a route, you may need to determine whether there is a previous route in the Navigator stack.
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
This can be useful when implementing custom navigation controls.
28. Navigator.push() vs pushReplacement()
| Method | Behavior | Typical Use |
push() | Adds a new route | Home → Details |
pushReplacement() | Replaces current route | Login → Home |
pushAndRemoveUntil() | Adds route and removes routes based on condition | Checkout → Home |
pop() | Removes current route | Details → Home |
popUntil() | Removes routes until a condition is met | Return to first route |
29. Navigator.push() vs Navigator.pop()
| push() | pop() |
| Adds a route | Removes a route |
| Moves forward | Moves backward |
| Opens a new screen | Closes the current screen |
| Returns a Future | Can return a result |
| Increases stack size | Decreases stack size |
30. Navigator Stack Example
Step 1:
[Home]
Step 2:
Navigator.push(Products)
[Home]
[Products]
Step 3:
Navigator.push(Details)
[Home]
[Products]
[Details]
Step 4:
Navigator.pop()
[Home]
[Products]
Step 5:
Navigator.pop()
[Home]
31. Practical Product Navigation Example
class ProductListScreen extends StatelessWidget {
const ProductListScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
),
body: ListView(
children: [
ListTile(
leading: const Icon(Icons.phone_android),
title: const Text('Smartphone'),
subtitle: const Text('₹25,000'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductDetailsScreen(
name: 'Smartphone',
price: 25000,
),
),
);
},
),
ListTile(
leading: const Icon(Icons.laptop),
title: const Text('Laptop'),
subtitle: const Text('₹60,000'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductDetailsScreen(
name: 'Laptop',
price: 60000,
),
),
);
},
),
],
),
);
}
}
class ProductDetailsScreen extends StatelessWidget {
final String name;
final double price;
const ProductDetailsScreen({
super.key,
required this.name,
required this.price,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Product Details'),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Text(
'Price: ₹$price',
style: const TextStyle(
fontSize: 20,
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Back to Products'),
),
],
),
),
);
}
}
32. Passing Objects Through Navigator.push()
For larger applications, passing a complete model object is often cleaner than passing many individual values.
class Course {
final String title;
final String description;
final double price;
const Course({
required this.title,
required this.description,
required this.price,
});
}
Push the course:
final course = Course(
title: 'Flutter Training',
description: 'Learn Flutter application development.',
price: 15000,
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CourseDetailsScreen(
course: course,
),
),
);
33. Returning a Boolean Result
A common use case is asking the user to confirm an action.
final confirmed = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ConfirmationScreen(),
),
);
if (confirmed == true) {
print('User confirmed');
} else {
print('User cancelled');
}
On the confirmation screen:
ElevatedButton(
onPressed: () {
Navigator.pop(context, true);
},
child: const Text('Confirm'),
)
Cancel button:
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text('Cancel'),
)
34. Important Difference Between pop() and pushReplacement()
Consider:
[Home]
[Login]
If you call:
Navigator.pop(context);
the Login route is removed and Home becomes visible.
If you call:
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
);
the current route is replaced with Home.
Choosing between them depends on the desired navigation history.
35. Common Mistakes with Navigator.push()
Mistake 1: Forgetting BuildContext
Navigation requires a valid BuildContext.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
Mistake 2: Forgetting the Destination Screen
The route needs a widget to build:
builder: (context) => const DetailsScreen()
Mistake 3: Pushing the Same Screen Repeatedly
Repeatedly calling push() can create many duplicate routes in the navigation stack.
Mistake 4: Using pop() Without Considering the Stack
Before custom back navigation, you can check:
Navigator.canPop(context)
Mistake 5: Ignoring the Returned Future
If the destination screen returns information, await the Future returned by Navigator.push().
36. Navigator.push() and Async Programming
Because Navigator.push() returns a Future, it can be used with async and await.
Future openDetails() async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
print(result);
}
The Future completes when the destination route is popped.
37. Safe Context Usage After await
If an asynchronous navigation result is followed by a widget update, check that the widget is still mounted before using its context or state.
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
if (!mounted) return;
setState(() {
selectedValue = result ?? '';
});
This helps avoid using a State object after it has been removed from the widget tree.
38. Navigator and Bottom Navigation
NavigationBar and Navigator can be used together.
For example:
Home
Products
Orders
Profile
|
+-- Settings
+-- Edit Profile
The NavigationBar can switch between top-level sections, while Navigator.push() can open detail screens inside a section.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SettingsScreen(),
),
);
39. Navigator and Forms
Navigation is frequently used when opening forms.
ElevatedButton(
onPressed: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const EditProfileScreen(),
),
);
if (!mounted) return;
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(result),
),
);
}
},
child: const Text('Edit Profile'),
)
The form screen can return a message:
Navigator.pop(
context,
'Profile updated successfully',
);
40. Navigator and Dialogs
Some Flutter APIs such as dialogs and modal bottom sheets also use route-based mechanisms internally.
For example:
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Delete Item?'),
content: const Text(
'Do you want to delete this item?',
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Delete'),
),
],
);
},
);
Here, Navigator.pop() dismisses the dialog route.
41. Named Routes and Navigator
Flutter supports named routes through Navigator.pushNamed().
MaterialApp(
routes: {
'/': (context) => const HomeScreen(),
'/details': (context) => const DetailsScreen(),
},
);
Navigate using:
Navigator.pushNamed(
context,
'/details',
);
Return using:
Navigator.pop(context);
Named routes remain part of Flutter, but current Flutter documentation does not recommend them for most new applications. For many new applications, Flutter recommends using Navigator with MaterialPageRoute or a routing package such as go_router, particularly when advanced routing or deep linking is required. Flutter Navigation and Routing
42. Navigator.push() with Named Routes
Navigator.pushNamed(
context,
'/profile',
);
This approach can be useful when working with an existing application that already uses named routes.
For new applications, direct route construction or a modern routing solution may be more appropriate depending on the application's requirements.
43. Nested Navigator
Large applications may contain more than one Navigator. A nested Navigator can manage navigation within a particular section or workflow.
Main Navigator
│
├── Home
├── Products
└── Profile
│
└── Nested Navigator
├── Profile Home
├── Edit Profile
└── Settings
This approach can be useful when an individual section needs its own navigation history.
44. NavigatorObserver
NavigatorObserver can be used to observe navigation events such as routes being pushed or popped.
class MyNavigatorObserver extends NavigatorObserver {
@override
void didPush(Route route, Route? previousRoute) {
print('Route pushed: ${route.settings.name}');
}
@override
void didPop(Route route, Route? previousRoute) {
print('Route popped: ${route.settings.name}');
}
}
It can be registered with MaterialApp:
MaterialApp(
navigatorObservers: [
MyNavigatorObserver(),
],
home: const HomeScreen(),
);
This can be useful for analytics, logging, and monitoring navigation behavior.
45. Practical E-Commerce Navigation Flow
Home
|
+-- Products
| |
| +-- Product Details
| |
| +-- Add to Cart
|
+-- Cart
| |
| +-- Checkout
| |
| +-- Payment
| |
| +-- Order Success
|
+-- Profile
|
+-- Edit Profile
+-- Settings
Different Navigator operations can be used for different stages of this flow.
| Action | Navigation Method |
| Open Product Details | push() |
| Return to Products | pop() |
| Login → Home | pushReplacement() |
| Checkout → Home after completion | pushAndRemoveUntil() |
| Return to first screen | popUntil() |
46. Complete Mini Project
import 'package:flutter/material.dart';
void main() {
runApp(const NavigationApp());
}
class NavigationApp extends StatelessWidget {
const NavigationApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Navigator Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
),
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Welcome to the Application',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const DetailsScreen(),
),
);
if (!context.mounted) return;
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(result),
),
);
}
},
child: const Text('Open Details'),
),
],
),
),
);
}
}
class DetailsScreen extends StatelessWidget {
const DetailsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Details'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'This is the Details Screen',
style: TextStyle(fontSize: 20),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(
context,
'Returned from Details Screen',
);
},
child: const Text('Return Data'),
),
const SizedBox(height: 10),
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go Back'),
),
],
),
),
);
}
}
47. Execution Flow of the Mini Project
- The application starts on
HomeScreen.
- The user taps Open Details.
Navigator.push() adds DetailsScreen to the stack.
- The Details screen appears.
- The user can press Go Back.
Navigator.pop() removes the Details route.
- The user can also press Return Data.
Navigator.pop(context, result) returns a String.
- The Future returned by
Navigator.push() receives that String.
- The Home screen displays the returned result.
48. Important Navigator Methods
| Method | Description |
push() | Adds a route to the navigation stack. |
pop() | Removes the current route. |
pushReplacement() | Replaces the current route with another route. |
pushAndRemoveUntil() | Pushes a route and removes previous routes according to a condition. |
popUntil() | Pops routes until a condition is satisfied. |
pushNamed() | Pushes a route using a registered route name. |
canPop() | Checks whether the Navigator can pop a route. |
of() | Retrieves the nearest Navigator. |
49. Common Interview Questions
Q1. What is Navigator in Flutter?
Navigator is a widget that manages a stack of routes and provides methods for navigating between screens.
Q2. What does Navigator.push() do?
It adds a new route to the Navigator stack and displays it above the current route.
Q3. What does Navigator.pop() do?
It removes the current route from the stack and reveals the previous route.
Q4. What is MaterialPageRoute?
It is a route implementation that provides Material-style page transitions.
Q5. Can Navigator.pop() return data?
Yes. A value can be supplied as the second argument:
Navigator.pop(context, result);
Q6. Can Navigator.push() receive returned data?
Yes. Navigator.push() returns a Future that completes when the route is popped.
Q7. What is the difference between push() and pop()?
push() adds a route, while pop() removes the current route.
Q8. What is pushReplacement()?
It replaces the current route with a new route.
Q9. What is pushAndRemoveUntil()?
It pushes a new route and removes previous routes until the supplied condition is satisfied.
Q10. What is popUntil()?
It removes routes until a specified condition becomes true.
50. Practice Exercises
- Create a Home screen and Details screen.
- Navigate from Home to Details using
Navigator.push().
- Return to Home using
Navigator.pop().
- Create a Product model and pass it to Product Details.
- Return a selected product from a selection screen.
- Create Login and Home screens and use
pushReplacement().
- Create Home, Cart, Checkout, and Success screens.
- Use
pushAndRemoveUntil() after checkout.
- Use
popUntil() to return to the Home screen.
- Implement a confirmation screen that returns a Boolean result.
- Create a reusable navigation helper method.
- Experiment with
MaterialPageRoute and CupertinoPageRoute.
- Implement a nested Navigator for a Profile section.
- Add a NavigatorObserver to log route changes.
51. Best Practices
- Use
Navigator.push() when adding a new screen to the navigation stack.
- Use
Navigator.pop() to return to the previous screen.
- Use meaningful route structures for larger applications.
- Pass strongly typed objects when practical.
- Use generic types when returning values from routes.
- Use
pushReplacement() when the current route should no longer remain in the normal back stack.
- Use
pushAndRemoveUntil() for flows where previous routes should be removed.
- Use
popUntil() when returning to a specific point in the navigation stack.
- Check
mounted after awaiting navigation results before updating widget state.
- Use nested navigation when a subsection requires an independent navigation flow.
- For complex deep-linking requirements, consider Router-based navigation or a routing package such as
go_router.
52. Quick Revision
| Concept | Meaning |
| Navigator | Manages navigation routes as a stack. |
| Route | Represents a screen/page in navigation. |
| push() | Adds a new route. |
| pop() | Removes the current route. |
| pushReplacement() | Replaces the current route. |
| pushAndRemoveUntil() | Pushes a route and removes previous routes according to a condition. |
| popUntil() | Pops routes until a condition is met. |
| MaterialPageRoute | Material-style route. |
| CupertinoPageRoute | Cupertino-style route. |
| canPop() | Checks whether the current Navigator can pop. |
| pushNamed() | Pushes a registered named route. |
53. Key Takeaways
- Flutter uses a Navigator to manage a stack of routes.
Navigator.push() opens a new screen by adding a route to the stack.
Navigator.pop() closes the current screen by removing its route.
- A route can return data through
Navigator.pop(context, result).
- The Future returned by
Navigator.push() can receive that returned result.
MaterialPageRoute is commonly used for Material applications.
CupertinoPageRoute provides Cupertino-style transitions.
pushReplacement() is useful when replacing the current navigation entry.
pushAndRemoveUntil() is useful for clearing previous navigation history according to a condition.
popUntil() can return to an earlier point in the navigation stack.
- Navigator can be combined with bottom navigation and nested navigation.
- For advanced navigation and deep-linking requirements, Flutter supports Router-based navigation and routing packages.
54. Useful Resources
Flutter Navigation and Routing: Official Flutter Navigation Documentation
Navigator API: Official Navigator API Documentation
Navigate to a New Screen and Back: Flutter Navigation Basics
Passing Data: Send Data to a New Screen
Returning Data: Return Data from a Screen
JustAcademy Flutter Training: JustAcademy Flutter Training Course
Register for Flutter Course Demo: Register for Flutter Course Demo